﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using ExtensionMethods;
using Lap.Collections;
using Lap.Extensions.Core;

namespace PpSmsSender
{
    public class Configuration : PpSmsSender.IConfiguration
    {
        public class Priority
        {
            public int Reserve { get; set; }

            public DateTimeRangeSet Schedule { get; set; }
        }

        public string ModemNamePattern;

        public Configuration()
        {
            this.Modems = new List<Modem>();
        }

        private List<Priority> _priorities = new List<Priority>();

        public List<Modem> Modems { get; set; }

        public List<Priority> Priorities
        {
            get { return _priorities; }
            set { _priorities = value; }
        }

        public static Configuration Read(string filename)
        {
            Configuration configuration = new Configuration();


            DateTime starttime = new DateTime();
            DateTime endtime = starttime.AddDays(1);
            DateTimeRangeSet fullday = new DateTimeRangeSet(new DateTimeRange(starttime, endtime));

            configuration._priorities = new List<Priority>();
            Range.Repeat(15).ForEach(i => configuration._priorities.Add(new Priority { Reserve = 0, Schedule = fullday }));


            XDocument xdoc = XDocument.Load(filename);
            try
            {
                xdoc
                    .Elements("modems")
                    .Elements("priorities")
                    .Elements("prio")
                    .ForEach(m =>
                                 {
                                     var seq = m.Attribute("seq") != null ? m.Attribute("seq").Value.Trim().Parse(0) : 0;
                                     var schedule = m.Attribute("schedule") != null ? m.Attribute("schedule").Value.Trim() : "";
                                     var reserve = m.Attribute("reserve") != null ? m.Attribute("reserve").Value.Trim().Parse(0) : 0;

                                     configuration._priorities[seq - 1].Schedule = DateTimeRangeSet.ParseRangeSet(schedule, DateTimeRange.EndFormat.LastMinute);
                                     configuration._priorities[seq - 1].Reserve = reserve;
                                 });
                var aDayLength = TimeSpan.FromDays(1);
                configuration._priorities.ToList()
                    .ForEach((p, ix) =>
                                 {
                                     if (p.Reserve != 0)
                                     {
                                         configuration._priorities.Skip(ix + 1)
                                             .TakeWhile((p2, ix2) => p2.Reserve == 0)
                                             .ForEach(p2 => p2.Reserve = p.Reserve);
                                     }
                                     if (p.Schedule.Length != aDayLength
                                         || p.Schedule.Start != DateTime.MinValue)
                                     {
                                         configuration._priorities.Skip(ix + 1)
                                             .TakeWhile((p2, ix2) =>
                                                  p2.Schedule.Length == aDayLength
                                                 && p2.Schedule.Start == DateTime.MinValue)
                                             .ForEach(p2 => p2.Schedule = p.Schedule);
                                     }
                                 });

                xdoc
                    .Elements("modems")
                    .Elements("modem")
                    .ForEach(m =>
                                 {
                                     Modem modem =
                                         new Modem
                                             {
                                                 ModemSeq = m.Attribute("seq").Value.Parse(0),
                                                 PhoneNumber = m.Attribute("number") != null? m.Attribute("number").Value : "",
                                                 Serial =   m.Attribute("serial") != null? m.Attribute("serial").Value : "",
                                                 DevicePattern = m.Attribute("devicepattern") != null ? m.Attribute("devicepattern").Value : "",
                                                 PeriodLimit = m.Attribute("periodlimit") != null ? m.Attribute("periodlimit").Value.Trim().Parse(0) : 0,
                                                 IsRecieveOnly = m.Attribute("isrecieveonly") != null && (m.Attribute("isrecieveonly").Value + "").Trim() == "1",
                                                 Sender = m.Element("controlmessages").Attribute("sender").Value.ToLowerInvariant().Trim(),
                                                 LastCounterReset = m.Attribute("lastperiodstart") != null ? m.Attribute("lastperiodstart").Value.Trim().ParseDate("yyyyMMddHHmmss") : DateTime.MinValue,
                                                 IsPaused = false,
                                             };
                                     modem.SetSentSinceFilled(m.Attribute("sentsincefilled") != null ? m.Attribute("sentsincefilled").Value.Trim().Parse(0) : 0);
                                     m.Elements("controlmessages")
                                         .Elements()
                                         .ForEach(c => modem.ControlMessages[c.Name.LocalName].Add(
                                             new ControlMessage
                                             {
                                                 IsRegexp = c.Attribute("regex") != null && c.Attribute("regex").Value + "" == "1",
                                                 Text = c.Value.Trim()
                                             }));
                                     m.Elements("operatorrequests")
                                         .Elements()
                                         .ForEach(c => modem.OperatorRequests[c.Name.LocalName]=
                                             new OperatorRequest
                                             {
                                                 IsRegexp = c.Attribute("regex") != null && c.Attribute("regex").Value + "" == "1",
                                                 Ussd = c.Attribute("ussd").Value,
                                                 Text = c.Value.Trim()
                                             });

                                     configuration.Modems.Add(modem);
                                 });
                return configuration;
            }
            catch (Exception ex)
            {
                throw new FormatException("Modems.xml is not valid", ex);
            }
        }


        public bool IsSendAlllowed(int prio, DateTime now)
        {
            DateTime asPureTime = DateTime.MinValue.Add(now.TimeOfDay);
            if (this.Priorities[prio].Reserve >= this.LeftToSendInPeriod)
                return false;

            var schedule = this.Priorities[prio].Schedule;
            if (schedule.Contains(now))
            {
                return true;
            }
            if (schedule.Contains(asPureTime))
            {
                return true;
            }
            return false;
        }

        public int LeftToSendInPeriod
        {
            get { return this.Modems.Sum(m => m.PeriodLimit) - this.Modems.Sum(m => m.SentSinceFilled); }
        }

        public static void Save(Configuration configuration, string filename)
        {

            XDocument xdoc = new XDocument();
            XElement modems = new XElement("modems");
            xdoc.Add(modems);
            XElement priorities = new XElement("priorities");
            modems.Add(priorities);
            var minDate1 = DateTime.MinValue.ToString("yyyy-MM-dd ");
            var minDate2 = DateTime.MinValue.AddDays(1).ToString("yyyy-MM-dd ");
            configuration._priorities
                .ForEach((pr, ix) =>
                             {
                                 XElement p = new XElement("prio");
                                 priorities.Add(p);
                                 p.SetAttributeValue("seq", "" + (1 + ix));
                                 var sched = pr.Schedule.ToString("yyyy-MM-dd HH:mm", linesep: ",", endformat: DateTimeRange.EndFormat.LastMinute).TrimEnd(',');
                                 sched = sched.Replace(minDate1, "");
                                 sched = sched.Replace(minDate2, "");
                                 if (sched.StartsWith("--"))
                                     sched = "00:00" + sched;
                                 p.SetAttributeValue("schedule", "" + sched);
                                 p.SetAttributeValue("reserve", "" + pr.Reserve);
                             });
            configuration.Modems
                .ForEach(mo =>
                             {
                                 XElement m = new XElement("modem");
                                 modems.Add(m);
                                 m.SetAttributeValue("seq", "" + mo.ModemSeq);
                                 m.SetAttributeValue("number", mo.PhoneNumber);
                                 m.SetAttributeValue("serial", mo.Serial);
                                 m.SetAttributeValue("devicepattern", mo.DevicePattern);
                                 m.SetAttributeValue("sentsincefilled", "" + mo.SentSinceFilled);
                                 m.SetAttributeValue("periodlimit", "" + mo.PeriodLimit);
                                 m.SetAttributeValue("isrecieveonly", mo.IsRecieveOnly ? "1" : "0");

                                 XElement messages = new XElement("controlmessages");
                                 m.Add(messages);
                                 m.Element("controlmessages").SetAttributeValue("sender", mo.Sender.ToLowerInvariant().Trim());
                                 mo.ControlMessages
                                     .ForEach(kvp1 =>
                                              kvp1.Value
                                                  .ForEach(mess =>
                                                  {
                                                      XElement cm = new XElement(kvp1.Key);
                                                      messages.Add(cm);
                                                      cm.Value = mess.Text;
                                                      cm.SetAttributeValue("regex", mess.IsRegexp ? "1" : "0");
                                                  }));

                                 XElement requests = new XElement("operatorrequests");
                                 m.Add(requests);
                                 mo.OperatorRequests
                                     .ForEach(kvp1 =>
                                                  {
                                                      var mess = kvp1.Value;
                                                      XElement cm = new XElement(kvp1.Key);
                                                      requests.Add(cm);
                                                      cm.Value = mess.Text;
                                                      cm.SetAttributeValue("regex", mess.IsRegexp ? "1" : "0");
                                                      cm.SetAttributeValue("ussd", mess.Ussd);
                                                  });
                             });
            xdoc.Save(filename);
        }

        #region Nested type: ControlMessage
        public class ControlMessage
        {
            public static readonly string StartMsg = "startmsg";
            public static readonly string StopMsg = "stopmsg";
            public static readonly string WarnMsg = "warnmsg";
            public bool IsRegexp;
            public string Text;
            private Regex _regexp;

            public bool IsMatch(string text)
            {
                if (this.IsRegexp)
                {
                    if (this._regexp == null)
                    {
                        this._regexp = new Regex(this.Text, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                    }
                    return this._regexp.IsMatch(text);
                }
                return text.Contains(this.Text);
            }
        }
        #endregion
        public class OperatorRequest
        {
            public static readonly string CheckMsg = "checkmsg";

            public bool IsRegexp;
            public string Text;
            private Regex _regexp;
            public string Ussd;

            public Regex Regex
            {
                get
                {
                    if (this._regexp == null && this.IsRegexp)
                    {
                        this._regexp = new Regex(this.Text, RegexOptions.IgnoreCase | RegexOptions.Compiled);
                    }
                    return this._regexp;
                }
            }

            public bool IsMatch(string text)
            {
                if (this.IsRegexp)
                {
                   
                    return this.Regex.IsMatch(text);
                }
                return text.Contains(this.Text);
            }
        }

        #region Nested type: Modem
        public class Modem : IConfigModemReadOnly
        {
            public Modem()
            {
                this.ControlMessages = new DictionaryWithDefault<string, List<ControlMessage>>(k => new List<ControlMessage>());
                this.OperatorRequests = new DictionaryWithDefault<string, OperatorRequest>(k=>new OperatorRequest());
            }

            public int ModemSeq { get; set; }
            public string Sender { get; set; }
            public DictionaryWithDefault<string, List<ControlMessage>> ControlMessages { get; set; }
            public DictionaryWithDefault<string, OperatorRequest> OperatorRequests { get; set; }
            public string DevicePattern { get; set; }

            //----- threadsafe
            private volatile bool _isPaused;
            private volatile bool _isRecieveOnly;
            private volatile int _sentSinceFilled;


            public DateTime LastCounterReset;
            public string Serial { get; set; }


            //---- following is eigther safed or only written when modem is not running
            #region IConfigModemReadOnly Members
            private volatile PhoneNumber _phoneNumber;
            public PhoneNumber PhoneNumber
            {
                get { return this._phoneNumber; }
                set { this._phoneNumber = value; }
            }

            public bool IsPaused
            {
                get { return _isPaused; }
                set { _isPaused = value; }
            }
            public bool IsRecieveOnly
            {
                get { return this._isRecieveOnly; }
                set { this._isRecieveOnly = value; }
            }


            private readonly object _sentSinceFilledLock = new object();

            public int SetSentSinceFilled(int value)
            {
                lock (this._sentSinceFilledLock)
                {
                    this._sentSinceFilled = value;
                    return this._sentSinceFilled;
                }
            }

            public int IncrementSentSinceFilled(int incr)
            {
                lock (this._sentSinceFilledLock)
                {
                    this._sentSinceFilled += incr;
                    return this._sentSinceFilled;
                }
            }


            public int SentSinceFilled
            {
                get { return this._sentSinceFilled; }
            }

            public TimeSpan MaxWaitForAcknowledge
            {
                get { return TimeSpan.FromSeconds(Debugger.IsAttached ? 180 : 60); }
            }
            public int PeriodLimit { get; set; }

            #endregion
        }
        #endregion
    }
}